/* Imitation of the puzzle box project */
/* Version 1 is the initial version after all
   testing has been completed.
   Version 2 updates verbage displayed, adds the
   ability to display text a letter at a time, and
   makes slight changes to the back door display. */

#include <EEPROM.h>
#include <LiquidCrystal.h>
#include <PWMServo.h>
#include <SoftwareSerial.h>
#include <Streaming.h>
#include <TinyGPS.h>
#include "PuzzleSetup.h"

// Initializations and Declarations
int servoPin = 10;
int pololuSwitch = 13;
int attemptAddress = 20;
int solvedFlagAddress = 30;
int attempts;
unsigned long fix_age;
float lat, lon, distance;
boolean puzzleSolved;

LiquidCrystal lcd(4,5,6,7,8,9);
PWMServo lockServo;
SoftwareSerial nss(GPSRXPIN,GPSTXPIN);
TinyGPS gps;
String top;
String bottom;

void setup() {
    // random seed
    randomSeed(analogRead(0));
    // communication with the LCD module
    lcd.begin(16,2);
    lcd.clear();
    // communication with the GPS module
    nss.begin(GPSBAUD);
    // define servo pin and low power switch pin
    lockServo.attach(servoPin);
    pinMode(pololuSwitch,OUTPUT);
    // low on the pin keeps switch on
    digitalWrite(pololuSwitch,LOW);

    // read in the number of attempts, if new, set to 0
    attempts = EEPROM.read(attemptAddress);
    if (attempts == 0xFF)
        attempts = 0;
    // read in puzzle solved flag, if new, set to false
    puzzleSolved = EEPROM.read(solvedFlagAddress);
    if (puzzleSolved == 0xFF)
        puzzleSolved = false;
    
    
    if (puzzleSolved) {
        // open box and display congratulatory message
        lockServo.write(OPENPOSITION);
        displayMessage(lcd,"Congratulations!"," You Solved It!",0,3000);
        // turn off the game
        powerDown();
    }
    else { // (re)lock box if puzzle not solved
        lockServo.write(LOCKEDPOSITION);
        
        // check number of attempts against the maximum
        if (attempts >= MAXATTEMPTS) {
            // displaying losing message
            displayMessage(lcd," Sorry, no more"," attempts left!",DISPDEL,3000);
            displayMessage(lcd,"You didn't solve","   the puzzle",DISPDEL,3000);
            displayMessage(lcd,"   Puzzle box"," closed forever",DISPDEL,3000);
            // power off
            powerDown();
        } else {
            // increment counter and store
            attempts += 1;
            EEPROM.write(attemptAddress,attempts);
            // show greeting
            displayMessage(lcd,"Shall we play","a game?",DISPDEL,3000);
            // use streaming4 library to print data easily to the LCD
            lcd.clear();
            lcd << "    Attempt:    ";
            lcd.setCursor(0,1);
            lcd << "    " << attempts << " of " << MAXATTEMPTS;
            delay(3000);
        }
    }
    // searching message while waitng for GPS signal
    displayMessage(lcd,"  Searching..."," Please Wait...",0,0);
}


void loop() {
    if (nss.available() && gps.encode(nss.read())) {
        // get current position
        gps.f_get_position(&lat, &lon, &fix_age);
        if (fix_age != TinyGPS::GPS_INVALID_AGE) {
            // compute distance to position
            distance = gps.distance_between(lat,lon,GAMELAT,GAMELON);
            
            if (distance <= SOLVERADIUS) { // solved the puzzle
                displayMessage(lcd,"Congratulations!"," You Solved It!",DISPDEL,1000);
                // set as solved
                EEPROM.write(solvedFlagAddress,true);
                displayMessage(lcd," Opening Box..."," ",DISPDEL,2000);
                // open box
                lockServo.write(OPENPOSITION);
                // turn off game
                powerDown();
            } else {
                // display distance to goal
                lcd.clear();
                lcd << "Distance: ";
                lcd.setCursor(0,1);
                lcd << distance/1000 << " km";
                delay(5000);
                displayMessage(lcd," Access Denied!"," ",DISPDEL,3000);
                // display a final hint to help out
                if (attempts == MAXATTEMPTS)
                    displayMessage(lcd,"   Puzzle box"," closed forever",DISPDEL,3000);
                else if (attempts > (MAXATTEMPTS-5))
                    displayMessage(lcd,"Hint: Compass","circles and map",DISPDEL,3000);
                // power off
                powerDown();
            }
        }
    }
    
    // turn off if no signal found for more than 5 minutes since power on
    if (millis() == 300000) {
        // display a vague hint message
        displayMessage(lcd,"No Signal Found"," ",DISPDEL,3000);
        // if GPS signal hasn't been seen after 5 attempts, show better hint
        if (attempts > 5)
            displayMessage(lcd,"  Hint: Need a","   clear view",DISPDEL,10000);
        powerDown();
    }
}


void displayMessage(LiquidCrystal lcd, String top, String bottom, int dispDel, int del) {
    lcd.clear();
    // print top line
    for (int i = 0; i < top.length(); i++) {
        lcd.print(top[i]);
        delay(dispDel);
    }

    // move to the next line
    lcd.setCursor(0,1);
    
    // print bottom line
    for (int i = 0; i < bottom.length(); i++) {
        lcd.print(bottom[i]);
        delay(dispDel);
    }

    delay(del);
}


void powerDown() {
    displayMessage(lcd,"Powering Down..."," ",DISPDEL,2000);
    // pull pololu pin high to turn off
    digitalWrite(pololuSwitch,HIGH);
    
    // if we get here, external power has been applied
    // display overvoltage test message to try to fool them
    displayMessage(lcd,"OVERVOLTAGE!!!","REMOVE POWER!!!",0,2000);
    // turn off display and print junk characters for TIMETORESET seconds
    //lcd.noDisplay();
    // get random characters to display to further worry them if they connect to a computer
    char junkTop[16], junkBottom[16];
    for(int i = 0; i < (TIMETORESET/2); i++) {
        for(int i = 0; i < 16; i++) {
            junkTop[i] = random(256);
            junkBottom[i] = random(256);
        }
        displayMessage(lcd,junkTop,junkBottom,0,1000);
    }
    for(int i = 0; i < (TIMETORESET); i++) {
        for(int i = 0; i < 16; i++) {
            junkTop[i] = random(256);
            junkBottom[i] = random(256);
        }
        displayMessage(lcd,junkTop,junkBottom,0,500);
    }
   
    // turn display back on and display a reset message
    //lcd.display();
    displayMessage(lcd," Attempts Reset","   To Zero...",0,0);
    
    // open box
    lockServo.write(OPENPOSITION);
    // reset attempts to 0 and solved to false
    EEPROM.write(attemptAddress,0);
    EEPROM.write(solvedFlagAddress,false);
    // wait 10 seconds, then close and exit
    delay(10000);
    lockServo.write(LOCKEDPOSITION);
    displayMessage(lcd,"Cycle Power Now","",0,0);
    exit(1);
}
